package ex3.render.raytrace;

import java.util.*;
import math.*;

public class Scene implements IInitable
{
	protected Vec backgroundCol;
    protected Vec ambientLight;
    @SuppressWarnings("rawtypes")
	protected List surfaces;
    @SuppressWarnings("rawtypes")
	protected List lights;
    protected CameraObject camera;
    protected int maxRecursionLevel;
    protected String backgroundTex;
    protected int superSampWidth;
    protected int useAcceleration;

    @SuppressWarnings("rawtypes")
	public Scene()
    {
        maxRecursionLevel = 10;
        backgroundTex = null;
        superSampWidth = 1;
        useAcceleration = 0;
        backgroundCol = new Vec(0.5D, 0.5D, 0.5D);
        ambientLight = new Vec(0.5D, 0.5D, 0.5D);
        surfaces = new LinkedList();
        lights = new LinkedList();
        camera = new CameraObject();
    }

    public void init(@SuppressWarnings("rawtypes") Map attributes)
    {
        if(attributes.containsKey("background-col"))
            backgroundCol = new Vec((String)attributes.get("background-col"));
        if(attributes.containsKey("ambient-light"))
            ambientLight = new Vec((String)attributes.get("ambient-light"));
        if(attributes.containsKey("background-tex"))
            backgroundTex = (String)attributes.get("background-tex");
        if(attributes.containsKey("super-samp-width"))
            superSampWidth = Integer.parseInt((String)attributes.get("super-samp-width"));
        if(attributes.containsKey("use-acceleration"))
            useAcceleration = Integer.parseInt((String)attributes.get("use-acceleration"));
        if(attributes.containsKey("max-recursion-level"))
            maxRecursionLevel = Integer.parseInt((String)attributes.get("max-recursion-level"));
    }

    public RayImpact findIntersection(Ray ray, boolean backFace)
    {
        double minDistance = (1.0D / 0.0D);
        Surface minSurface = null;
        for(@SuppressWarnings("rawtypes")
		Iterator iterator = surfaces.iterator(); iterator.hasNext();)
        {
            Surface surface = (Surface)iterator.next();
            double d = surface.nearestIntersection(ray, backFace);
            if(minDistance > d && 0.0001D < d)
            {
                minDistance = d;
                minSurface = surface;
            }
        }

        if(Double.isInfinite(minDistance))
        {
            return null;
        } else
        {
            Point3D intersection = new Point3D(ray.p);
            intersection.mac(minDistance, ray.v);
            return new RayImpact(intersection, minSurface, minDistance);
        }
    }

    public Vec calcColor(RayImpact hit, Ray ray, int level)
    {
        if(level == maxRecursionLevel)
            return new Vec(0.0D, 0.0D, 0.0D);
        Vec I = new Vec();
        if(hit == null)
            return backgroundCol;
        Vec N = hit.surface.normalAt(hit.intersection, ray);
        I = Vec.add(hit.surface.material.emission, Vec.scale(hit.surface.material.ambient, ambientLight));
        for(int i = 0; i < lights.size(); i++)
        {
            Vec Id = new Vec();
            Vec Is = new Vec();
            Vec Il = new Vec();
            Vec L = new Vec();
            Vec R = new Vec();
            Vec V = new Vec();
            double Sl = 1.0D;
            Light light = (Light)lights.get(i);
            Il = light.getColor(hit.intersection);
            L = light.getDirection(hit.intersection);
            Ray rayToLight = new Ray(hit.intersection, L);
            RayImpact hasShadow = findIntersection(rayToLight, false);
            if(hasShadow != null)
                Sl = light.getShadow(hit.intersection, hasShadow.minDistance);
            double nDotL = N.dotProd(L);
            if(nDotL > 0.0001D)
                Id = Vec.scale(nDotL, hit.surface.material.diffuse);
            V = ray.v;
            V.normalize();
            R = L.reflect(N);
            double vDotR = V.dotProd(R);
            if(vDotR > 0.0001D)
                Is = Vec.scale(Math.pow(vDotR, hit.surface.material.shininess), hit.surface.material.specular);
            Vec sigma = Id.clone();
            sigma.add(Is);
            sigma.multiplyElement(Il);
            sigma.scale(Sl);
            I.add(sigma);
        }

        Vec clone = new Vec(ray.v);
        Vec reflection = clone.reflect(N);
        reflection.normalize();
        Ray reflectionRay = new Ray(hit.intersection, reflection);
        I.mac(hit.surface.material.reflectance, calcColor(findIntersection(reflectionRay, false), reflectionRay, level + 1));
        return I;
    }

    @SuppressWarnings("unchecked")
	public void addObjectByName(String name, @SuppressWarnings("rawtypes") Map attributes)
    {
        Surface surface = null;
        Light light = null;
        if("sphere".equals(name))
            surface = new Sphere();
        if("disc".equals(name))
            surface = new Disc();
        if("trimesh".equals(name))
        {
            for(@SuppressWarnings("rawtypes")
			Iterator iterator = attributes.keySet().iterator(); iterator.hasNext();)
            {
                String key = (String)iterator.next();
                if(key.startsWith("tri"))
                {
                    String val = (String)attributes.get(key);
                    Scanner s = new Scanner(val);
                    double tri1p0 = s.nextDouble();
                    double tri1p1 = s.nextDouble();
                    double tri1p2 = s.nextDouble();
                    double tri2p0 = s.nextDouble();
                    double tri2p1 = s.nextDouble();
                    double tri2p2 = s.nextDouble();
                    double tri3p0 = s.nextDouble();
                    double tri3p1 = s.nextDouble();
                    double tri3p2 = s.nextDouble();
                    Triangle tri = new Triangle(new Point3D(tri1p0, tri1p1, tri1p2), new Point3D(tri2p0, tri2p1, tri2p2), new Point3D(tri3p0, tri3p1, tri3p2));
                    tri.init(attributes);
                    surfaces.add(tri);
                }
            }

        }
        if("omni-light".equals(name))
            light = new OmniLight();
        if("dir-light".equals(name))
            light = new DirectionalLight();
        if("spot-light".equals(name))
            light = new SpotLight();
        if(surface != null)
        {
            surface.init(attributes);
            surfaces.add(surface);
        }
        if(light != null)
        {
            light.init(attributes);
            lights.add(light);
        }
    }

    public void setCameraAttributes(@SuppressWarnings("rawtypes") Map attributes)
    {
        camera.init(attributes);
    }
}